package org.zhaya.sdk.util;



import org.zhaya.sdk.constant.CalculatorOperatorEnum;

import java.math.BigDecimal;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class CalculatorUtil {

    private static final String NUM_REGEX = "\\d*\\.?\\d*";
    private static final String OP_REGEX = "[+\\-*/%^]";
    private static final Pattern opPattern = Pattern.compile("[+\\-*/%^()]");
    private static final Stack<BigDecimal> numbers = new Stack<>();
    private static final Stack<CalculatorOperatorEnum> ops = new Stack<>();
    private static final Stack<String> results = new Stack<>();
    private static final Pattern numPattern = Pattern.compile(NUM_REGEX);


    public static String apply(String input) {
        String postfixExpression = postfixExpression(input).replaceAll(" ", "");
        return calcPostfixExpression(postfixExpression);
    }

    private static String calcPostfixExpression(String postfixExpression) {
        String[] elements = postfixExpression.split(",");
        for (String ele : elements) {
            if (ele.matches(NUM_REGEX)) {
                double value = Double.parseDouble(ele);
                BigDecimal bv = BigDecimal.valueOf(value);
                numbers.push(bv);
            } else if (ele.matches(OP_REGEX)) {
                try {
                    CalculatorOperatorEnum op = CalculatorOperatorEnum.convert(ele);
                    BigDecimal top = numbers.pop();
                    BigDecimal prev = numbers.pop();
                    BigDecimal result = CalculatorOperatorEnum.calculate(op, prev, top);
                    numbers.push(result);
                } catch (CalculatorOperatorEnum.IllegalOperationException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return numbers.pop().stripTrailingZeros().toPlainString();
    }

    public static String postfixExpression(String input) {
        input = input.replaceAll(" ", "");
        Matcher numMatcher = numPattern.matcher(input);
        Matcher opm = opPattern.matcher(input);

        for (int pos = 0; pos < input.length(); ) {
            boolean found = numMatcher.find(pos);
            if (found) {
                int start = numMatcher.start();
                int end = numMatcher.end();
                pos = end;
                if (start != end) {
                    String sub = input.substring(start, end);
                    results.push(sub);
                }
            }
            found = opm.find(pos);
            if (found) {
                int start = opm.start();
                int end = opm.end();
                pos = end;
                if (start != end) {
                    try {
                        String sub = input.substring(start, end);
                        CalculatorOperatorEnum action = CalculatorOperatorEnum.convert(sub);
                        if (action == CalculatorOperatorEnum.LEFT) {
                            ops.push(action);
                        } else if (action == CalculatorOperatorEnum.RIGHT) {
                            CalculatorOperatorEnum ele;
                            do {
                                ele = ops.pop();
                                if (ele != CalculatorOperatorEnum.LEFT) {
                                    results.push(ele.getOperate());
                                }
                            } while (ele != CalculatorOperatorEnum.LEFT);
                        } else {
                            boolean actionPushed = false;
                            while (!actionPushed) {
                                if (ops.isEmpty()) {
                                    ops.push(action);
                                    actionPushed = true;
                                } else {
                                    CalculatorOperatorEnum top = ops.pop();
                                    if (CalculatorOperatorEnum.LEFT == top
                                            || action.getPriority() > top.getPriority()) {
                                        ops.push(top);
                                        ops.push(action);
                                        actionPushed = true;
                                    } else {
                                        results.push(top.getOperate());
                                    }
                                }
                            }
                        }
                    } catch (CalculatorOperatorEnum.IllegalOperationException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
        while (!ops.isEmpty()) {
            CalculatorOperatorEnum op = ops.pop();
            results.push(op.getOperate());
        }
        StringBuilder sb = new StringBuilder();
        while (!results.isEmpty()) {
            String ele = results.pop();
            sb.insert(0, ele.concat(", "));
        }
        int last = sb.lastIndexOf(", ");
        if (last != -1) {
            sb.delete(last, last + 2);
        }
        return sb.toString().trim();
    }
}

